package com.xuecheng.content.service.impl;

import com.alibaba.fastjson.JSON;
import com.xuecheng.base.exception.CommonError;
import com.xuecheng.base.exception.XueChengPlusException;
import com.xuecheng.content.config.MultipartSupportConfig;
import com.xuecheng.content.feignclient.MediaServiceClient;
import com.xuecheng.content.mapper.*;
import com.xuecheng.content.model.dto.CourseBaseInfoDto;
import com.xuecheng.content.model.dto.CoursePreviewDto;
import com.xuecheng.content.model.dto.TeachplanDto;
import com.xuecheng.content.model.po.CourseBase;
import com.xuecheng.content.model.po.CourseMarket;
import com.xuecheng.content.model.po.CoursePublish;
import com.xuecheng.content.model.po.CoursePublishPre;
import com.xuecheng.content.service.CourseBaseInfoService;
import com.xuecheng.content.service.CoursePublishService;
import com.xuecheng.content.service.TeachplanService;
import com.xuecheng.messagesdk.model.po.MqMessage;
import com.xuecheng.messagesdk.service.MqMessageService;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.Version;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class CoursePublishServiceImpl implements CoursePublishService {

    @Resource
    CourseBaseInfoService courseBaseInfoService;
    @Resource
    TeachplanService teachplanService;
    @Resource
    CourseMarketMapper courseMarketMapper;
    @Resource
    CoursePublishPreMapper coursePublishPreMapper;
    @Resource
    CoursePublishMapper coursePublishMapper;
    @Resource
    CourseTeacherMapper courseTeacherMapper;
    @Resource
    CourseBaseMapper courseBaseMapper;
    @Autowired
    RedisTemplate redisTemplate;

    @Resource//todo 依赖了相关板块,可以识别
    MqMessageService mqMessageService;

    @Resource
    MediaServiceClient mediaServiceClient;

    @Resource
    RedissonClient redissonClient;

    @Override//课程发布相关接口
    public CoursePreviewDto getCoursePreviewInfo(Long courseId) {
        //课程基本信息、营销信息
        CourseBaseInfoDto courseBaseInfo = courseBaseInfoService.getCourseBaseInfo(courseId);
        //课程计划信息
        List<TeachplanDto> teachplanTree= teachplanService.findTeachplanTree(courseId);
        CoursePreviewDto coursePreviewDto = new CoursePreviewDto();
        coursePreviewDto.setCourseBase(courseBaseInfo);
        coursePreviewDto.setTeachplans(teachplanTree);
        return coursePreviewDto;
    }

    @Override
    @Transactional
    public void commitAudit(Long companyId, Long courseId) {
//        把课程提交审核,首先应该考虑约束条件,如果已经在审核中
        CourseBaseInfoDto courseBaseInfo = courseBaseInfoService.getCourseBaseInfo(courseId);
        if (!companyId.equals(courseBaseInfo.getCompanyId())){
            XueChengPlusException.cast("你不是该机构机构");
        }
        if (courseBaseInfo == null){
            XueChengPlusException.cast("课程不存在");
        }
        String auditStatus = courseBaseInfo.getAuditStatus();
        if ("202003".equals(auditStatus)){
            XueChengPlusException.cast("课程已提交,请等待审核");
        }
//        课程的相关信息没有填完也不能提交 等等
        String pic = courseBaseInfo.getPic();
        if (StringUtils.isEmpty(pic)){
            XueChengPlusException.cast("请上传图片");
        }
        List<TeachplanDto> teachplanTree = teachplanService.findTeachplanTree(courseId);
        if (teachplanTree == null || teachplanTree.size() == 0){
            XueChengPlusException.cast("请编写课程计划");
        }
//        查询课程基本信息,营销信息(courseBaseInfo也有),课程分类,课程计划,todo 教师信息插入到预发布表
        CoursePublishPre coursePublishPre = new CoursePublishPre();
        BeanUtils.copyProperties(courseBaseInfo,coursePublishPre);
        coursePublishPre.setCompanyId(companyId);
        CourseMarket courseMarket = courseMarketMapper.selectById(courseId);
        String courseMarketJson = JSON.toJSONString(courseMarket);
        coursePublishPre.setMarket(courseMarketJson);
        String teachplanTreeJson = JSON.toJSONString(teachplanTree);
        coursePublishPre.setTeachplan(teachplanTreeJson);
//        todo 教师信息插入到预发布表
        coursePublishPre.setStatus("202003");
        coursePublishPre.setCreateDate(LocalDateTime.now());
//      查询预发布表,如果没有就插入预发布表,因为审核时可以修改数据,再次提交
        if (coursePublishPreMapper.selectById(courseId) == null){
            coursePublishPreMapper.insert(coursePublishPre);
        }else {
            coursePublishPreMapper.updateById(coursePublishPre);
        }
//        更新课程基本信息表改为已审核
        CourseBase courseBase = courseBaseMapper.selectById(courseId);
        courseBase.setAuditStatus("202003");
        courseBaseMapper.updateById(courseBase);
    }

    @Override
    @Transactional
    public void publish(Long companyId, Long courseId) {
        CoursePublishPre coursePublishPre = coursePublishPreMapper.selectById(courseId);
        if (courseId == null){
            XueChengPlusException.cast("无审核记录,不能发布");
        }
//        约束校验
        if (companyId.equals(coursePublishPre)){
            XueChengPlusException.cast("非本机构");
        }
        if (!"202004".equals(coursePublishPre.getStatus())){
            XueChengPlusException.cast("还未审核成功,不允许发布");
        }
//        向课程发布表写入数据
//        coursePublishPre.setStatus("203002");//已发布(老师不修改???)
        coursePublishPre.setAuditDate(LocalDateTime.now());
        CoursePublish coursePublish = new CoursePublish();
        BeanUtils.copyProperties(coursePublishPre,coursePublish);
        if(coursePublishMapper.selectById(courseId) == null){
            coursePublishMapper.insert(coursePublish);
        }else {
            coursePublishMapper.updateById(coursePublish);
        }
        // todo 向消息发布表 本地消息表加任务调度完成事务的最终一致性
        saveCoursePublishMessage(courseId);
        coursePublishPreMapper.deleteById(courseId);

    }


    /**
     * @description 保存消息表记录
     * @param courseId  课程id
     */
    private void saveCoursePublishMessage(Long courseId){
        MqMessage mqMessage = mqMessageService.addMessage("course_publish", String.valueOf(courseId), null, null);
        if(mqMessage==null){
            XueChengPlusException.cast(CommonError.UNKOWN_ERROR);
        }
    }

    @Override
    public File generateCourseHtml(Long courseId) {
//        Configuration configuration = new Configuration(Configuration.getVersion());
        Configuration configuration = new Configuration(new Version(2, 3, 7));
        //拿到classPath路径
        String classPath = this.getClass().getResource("/").getPath();
        //拿到指定模板的目录
        File htmlFile = null;
        try {
            configuration.setDirectoryForTemplateLoading(new File(classPath + "/templates/"));
            //指定编码
            configuration.setDefaultEncoding("utf-8");
//        得到模板
            Template template = configuration.getTemplate("course_template.ftl");
//        准备数据
//        CoursePreviewDto coursePreviewDto = coursePublishService.getCoursePreviewInfo(120L);
            CoursePreviewDto coursePreviewDto = getCoursePreviewInfo(courseId);
            HashMap<String, Object> map = new HashMap<>();
            map.put("model",coursePreviewDto);
//        往静态模板中放入数据
            String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);

            InputStream inputStream = IOUtils.toInputStream(html, "utf-8");
//            FileOutputStream fileOutputStream = new FileOutputStream(new File("D:\\zzz\\120.html"));
            htmlFile = File.createTempFile("coursepublish", ".html");
            FileOutputStream fileOutputStream = new FileOutputStream(htmlFile);
            IOUtils.copy(inputStream,fileOutputStream);
        } catch (Exception e) {
            log.error("页面静态化出现问题:{}",courseId);
            e.printStackTrace();
        }
        return htmlFile;

    }

    @Override
    public void uploadCourseHtml(Long courseId, File file) {
        try {
            //        将file转换为MultipartFile
            MultipartFile multipartFile = MultipartSupportConfig.getMultipartFile(file);
            String upload = mediaServiceClient.upload(multipartFile, "course/" + courseId + ".html");
            if(upload == null){
                log.debug("走了降级处理,课程ID:{}",courseId);
                XueChengPlusException.cast("出现异常,走了降级处理");
            }
        } catch (IOException e) {
            e.printStackTrace();
            XueChengPlusException.cast("出现异常,走了降级处理");
        }

    }
    @Override
    public CoursePublish getCoursePublish(Long courseId){
        CoursePublish coursePublish = coursePublishMapper.selectById(courseId);
        return coursePublish ;
    }

    @Override
    public CoursePublish getCoursePublishCache(Long courseId) {
//         首先查询缓存
        Object o = redisTemplate.opsForValue().get("course:" + courseId);
        if (o == null){//没有
            CoursePublish coursePublish = coursePublishMapper.selectById(courseId);
            if (coursePublish != null) {//不为空在存入redis,多一层保障
                String jsonString = JSON.toJSONString(coursePublish);
                redisTemplate.opsForValue().set("course:", jsonString);
            }
            return coursePublish;
        }else {
            CoursePublish coursePublish = JSON.parseObject(o.toString(), CoursePublish.class);
            return coursePublish;
        }
    }

//    todo 缓存穿透,存入一个有过期时间的null值
//    //查询缓存
//   Object  jsonObj = redisTemplate.opsForValue().get("course:" + courseId);
//    if(jsonObj!=null){
//    String jsonString = jsonObj.toString();
//        if(jsonString.equals("null"))
//            return null;
//        CoursePublish coursePublish = JSON.parseObject(jsonString, CoursePublish.class);
//        return coursePublish;
//    } else {
//        //从数据库查询
//        System.out.println("从数据库查询数据...");
//        CoursePublish coursePublish = getCoursePublish(courseId);
//        //设置过期时间300秒
//        redisTemplate.opsForValue().set("course:" + courseId, JSON.toJSONString(coursePublish),30, TimeUnit.SECONDS);
//        return coursePublish;
//    }


    //todo 设置不同的过期时间,缓存雪崩,还可以进行预热,定时把数据加入到redis中(定时也有讲究,不要在并发高的时候,)
//  redisTemplate.opsForValue().set("course:" + courseId, JSON.toJSONString(coursePublish),300+new Random().nextInt(100), TimeUnit.SECONDS);

//    通过锁的方式来解决缓存击穿
//    public  CoursePublish getCoursePublishCache2(Long courseId){
//        //查询缓存
//        Object  jsonObj = redisTemplate.opsForValue().get("course:" + courseId);
//        if(jsonObj!=null){
//            String jsonString = jsonObj.toString();
//            CoursePublish coursePublish = JSON.parseObject(jsonString, CoursePublish.class);
//            return coursePublish;
//        }else{
//            synchronized(this){
//                jsonObj = redisTemplate.opsForValue().get("course:" + courseId);
//                if(jsonObj!=null){
//                    String jsonString = jsonObj.toString();
//                    CoursePublish coursePublish = JSON.parseObject(jsonString, CoursePublish.class);
//                    return coursePublish;
//                }
//                System.out.println("=========从数据库查询==========");
//                //从数据库查询
//                CoursePublish coursePublish = getCoursePublish(courseId);
//                //设置过期时间300秒
//                redisTemplate.opsForValue().set("course:" + courseId, JSON.toJSONString(coursePublish),300, TimeUnit.SECONDS);
//                return coursePublish;
//            }
//        }
//    }

//    todo 上面的的方案用于多服务器的时候就会出现拦截不到的情况,因为该锁只能适用于本线程,还有不足,释放锁没有实现原子性,容易造成再次访问数据库
//    public  CoursePublish getCoursePublishCache3(Long courseId){
//        //查询缓存
//        Object  jsonObj = redisTemplate.opsForValue().get("course:" + courseId);
//        if(jsonObj!=null){
//            String jsonString = jsonObj.toString();
//            CoursePublish coursePublish = JSON.parseObject(jsonString, CoursePublish.class);
//            return coursePublish;
//        }else{
////            只有值不存在才能设值成功
//            Boolean lock = redisTemplate.opsForValue().setIfAbsent("coursequerylock:" + courseId, "01");
//            if (lock){
//                jsonObj = redisTemplate.opsForValue().get("course:" + courseId);
//                if(jsonObj!=null){
//                    String jsonString = jsonObj.toString();
//                    CoursePublish coursePublish = JSON.parseObject(jsonString, CoursePublish.class);
//                    return coursePublish;
//                }
//                System.out.println("=========从数据库查询==========");
//                //从数据库查询
//                CoursePublish coursePublish = getCoursePublish(courseId);
//                //设置过期时间300秒
//                redisTemplate.opsForValue().set("course:" + courseId, JSON.toJSONString(coursePublish),300, TimeUnit.SECONDS);
//                return coursePublish;
//            }
//        }
//    }


    //Redisson分布式锁
    public  CoursePublish getCoursePublishCache3(Long courseId){
        //查询缓存
        String jsonString = (String) redisTemplate.opsForValue().get("course:" + courseId);
        if(StringUtils.isNotEmpty(jsonString)){
            if(jsonString.equals("null")){
//                todo 返回null值如何处理
                return null;
            }
            CoursePublish coursePublish = JSON.parseObject(jsonString, CoursePublish.class);
            return coursePublish;
        }else{
            //每门课程设置一个锁
            RLock lock = redissonClient.getLock("coursequerylock:"+courseId);
            //获取锁
            lock.lock();
            try {
                jsonString = (String) redisTemplate.opsForValue().get("course:" + courseId);
                if(StringUtils.isNotEmpty(jsonString)){
                    CoursePublish coursePublish = JSON.parseObject(jsonString, CoursePublish.class);
                    return coursePublish;
                }
                System.out.println("=========从数据库查询==========");
                //从数据库查询
                CoursePublish coursePublish = getCoursePublish(courseId);
                redisTemplate.opsForValue().set("course:" + courseId, JSON.toJSONString(coursePublish),1,TimeUnit.DAYS);
                return coursePublish;
            }finally {
                //释放锁
                lock.unlock();
            }
        }
    }
}
